﻿//
// Copyright(C) MixModes Inc. 2011
// 

using System;
using System.Windows;
using System.Windows.Data;
using MixModes.Phone.Synergy.Utilities;

namespace MixModes.Phone.Synergy.Framework
{
    /// <summary>
    /// Wrapper class that creates a wrapper for DependencyPropertyDescriptor
    /// </summary>
    /// <remarks>Deriving from FrameworkElement or else BindingOperations.SetBinding fails</remarks>
    public class ObservableDependencyProperty : FrameworkElement, IDisposable
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ObservableDependencyProperty"/> class.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="onDependencyPropertyChanged">Dependency property changed callback</param>
        public ObservableDependencyProperty(string propertyName,
                                            DependencyPropertyChangedEventHandler onDependencyPropertyChanged)
        {
            Validate.StringNotNullOrEmpty(propertyName, "propertyName");
            _propertyName = propertyName;
            _onDependencyPropertyChanged = onDependencyPropertyChanged;
        }

        /// <summary>
        /// Gets the target
        /// </summary>
        public DependencyObject Target
        {
            get { return _target; }
        }

        /// <summary>
        /// Enables property monitoring for a dependency object
        /// </summary>
        /// <param name="dependencyObject">The dependency object</param>
        public void AddValueChanged(DependencyObject dependencyObject)
        {
            CreateBinding(dependencyObject);
        }

        /// <summary>
        /// Disables property monitoring for a dependency object
        /// </summary>
        public void RemoveValueChanged()
        {
            ClearBinding();
        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            ClearBinding();
        }

        /// <summary>
        /// Value that gets bound to dependency property of dependency object for change notification
        /// </summary>
        private static readonly DependencyProperty ValueProperty = DependencyProperty.RegisterAttached("Value",
                                                                                                       typeof(object),
                                                                                                       typeof(ObservableDependencyProperty),
                                                                                                       new PropertyMetadata(null, OnPropertyChanged));

        /// <summary>
        /// Called when value of dependency property has changed
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="args">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private static void OnPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
        {
            ObservableDependencyProperty property = sender as ObservableDependencyProperty;

            if ((property == null) || (property._changeEventInProgress))
            {
                return;
            }

            property._changeEventInProgress = true;
            property._onDependencyPropertyChanged(property._target, args);
            property._changeEventInProgress = false;
        }

        /// <summary>
        /// Creates the binding to monitor the dependency property through Value
        /// </summary>
        /// <param name="dependencyObject">The dependency object.</param>
        private void CreateBinding(DependencyObject dependencyObject)
        {
            _changeEventInProgress = true;
            Binding binding = new Binding(_propertyName);
            binding.Mode = BindingMode.OneWay;
            binding.Source = dependencyObject;
            BindingOperations.SetBinding(this, ValueProperty, binding);
            _target = dependencyObject;
            _changeEventInProgress = false;
        }

        /// <summary>
        /// Clears the binding.
        /// </summary>
        private void ClearBinding()
        {
            // This prevents null exception as event handler may have enumeration as property type
            _changeEventInProgress = true;
            ClearValue(ValueProperty);
            _changeEventInProgress = false;
        }

        // Private members
        private DependencyPropertyChangedEventHandler _onDependencyPropertyChanged;
        private bool _changeEventInProgress;
        private string _propertyName;
        private DependencyObject _target;
    }
}
